//
//  Connection.swift
//  SwifSoup
//
//  Created by Nabil Chatbi on 29/09/16.
//  Copyright © 2016 Nabil Chatbi.. All rights reserved.
//

import Foundation
//TODO:

public class Connection {
    
    private var urlString: String? = nil
    private var request: URLRequest? = nil
    private var httpBody: String? = nil
    private var encoding: CFStringEncodings? = nil
    
    public static func connect(_ url: URL) -> Connection {
        let connect = Connection()
        connect.request = URLRequest(url: url)
        connect.timeout(10)
        connect.userAgent("Mozilla/5.0 Chrome/69.0.3497.81 Safari/537.36")
        connect.httpBody = ""
        connect.urlString = url.absoluteString.urlDecoding()
        return connect
    }
    
    public static func connect(_ url: String)throws -> Connection {
        let url = url.urlEncoding()
        guard let u = URL(string: url) else {
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "url not init.")
        }

        return connect(u)
    }
    
    public func timeout(_ timeoutMillis: Double) -> Connection {
        request?.timeoutInterval = timeoutMillis
        return self
    }
    
    public func userAgent(_ agent: String) -> Connection {
        return header("User-Agent", agent)
    }
    
    public func referer(_ referer: String) -> Connection {
        return header("Referer", referer)
    }
    
    public func header(_ name: String, _ value: String) -> Connection {
        request?.addValue(value, forHTTPHeaderField: name)
        return self
    }
    
    public func cookie(_ name: String, _ value: String) -> Connection {
        if var cookie = request?.value(forHTTPHeaderField: "Cookie") {
            cookie.append("\(name)=\(value);")
            request?.addValue(cookie, forHTTPHeaderField: "Cookie")
        } else {
            request?.addValue("\(name)=\(value);", forHTTPHeaderField: "Cookie")
        }
        return self
    }
    
    public func data(_ name: String, _ value: String) -> Connection {
        if httpBody?.isEmpty == true {
            httpBody?.append("\(name)=\(value)")
        } else {
            httpBody?.append("&\(name)=\(value)")
        }
        
        return self
    }
    
    public func encoding(_ encoding: CFStringEncodings) -> Connection {
        self.encoding = encoding
        return self
    }
    
    public func doGet()throws -> String {
        if let urlString = self.urlString, let url = URL(string: urlString.urlEncoding(self.encoding)) {
            request?.url = url
        }
        
        guard var request = request, let url = request.url else {
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "request isn't init.")
        }
        
        var encoding: String.Encoding
        if let _encoding = self.encoding {
            let enc = CFStringConvertEncodingToNSStringEncoding(UInt32(_encoding.rawValue))
            encoding = String.Encoding(rawValue: enc)
        } else {
            encoding = String.Encoding.utf8
        }
        
        var response: URLResponse?
        do {
            request.httpMethod = "GET"
            let result = try NSURLConnection.sendSynchronousRequest(request, returning: &response)
            
            if let str = String(data: result, encoding: encoding) {
                print("response = \(str)")
                //                let doc = try Parser.parse(str, url.absoluteString)
                return str
            } else {
                throw Exception.Error(type: .IOException, Message: "response encoding error.")
            }
        } catch Exception.Error(let type, let message) {
            print("Exception: type=\(type), message=\(message)")
            throw Exception.Error(type: type, Message: message)
        } catch {
            print("Exception.")
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Exception")
        }
        
    }
    
    public func get()throws -> Document {
        do {
            let str = try doGet()
            let doc = try Parser.parse(str, request?.url?.absoluteString ?? "")
            return doc
        } catch Exception.Error(let type, let message) {
            print("Exception: type=\(type), message=\(message)")
            throw Exception.Error(type: type, Message: message)
        } catch {
            print("Exception.")
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Exception")
        }
        
    }
    
    public func doPost()throws -> String {
        if let urlString = self.urlString, let url = URL(string: urlString.urlEncoding(self.encoding)) {
            request?.url = url
        }
        
        var encoding: String.Encoding
        if let _encoding = self.encoding {
            let enc = CFStringConvertEncodingToNSStringEncoding(UInt32(_encoding.rawValue))
            encoding = String.Encoding(rawValue: enc)
        } else {
            encoding = String.Encoding.utf8
        }
        
        request?.httpBody = httpBody?.urlEncoding(self.encoding).data(using: encoding, allowLossyConversion: true)
        guard var request = request, let url = request.url else {
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "request isn't init.")
        }
        
        var response: URLResponse?
        do {
            request.httpMethod = "POST"
            let result = try NSURLConnection.sendSynchronousRequest(request, returning: &response)
            if let str = String(data: result, encoding: encoding) {
                print("response = \(str)")
                //                let doc = try Parser.parse(str, url.absoluteString)
                return str
            } else {
                throw Exception.Error(type: .IOException, Message: "response encoding error.")
            }
            
        } catch Exception.Error(let type, let message) {
            print("Exception: type=\(type), message=\(message)")
            throw Exception.Error(type: type, Message: message)
        } catch {
            print("Exception.")
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Exception")
        }
        
    }
    
    public func post()throws -> Document {
        do {
            let str = try doPost()
            let doc = try Parser.parse(str, request?.url?.absoluteString ?? "")
            return doc
        } catch Exception.Error(let type, let message) {
            print("Exception: type=\(type), message=\(message)")
            throw Exception.Error(type: type, Message: message)
        } catch {
            print("Exception.")
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "Exception")
        }
        
    }
    
    public func parse()throws -> Document {
        guard var request = request, let url = request.url else {
            throw Exception.Error(type: ExceptionType.IllegalArgumentException, Message: "request isn't init.")
        }
        
        do {
            if let httpBody = self.httpBody, !httpBody.isEmpty {
                let doc = try post()
                return doc
            } else {
                let doc = try get()
                return doc
            }
        } catch Exception.Error(let type, let message) {
            print("Exception: type=\(type), message=\(message)")
            throw Exception.Error(type: type, Message: message)
        }
        
    }

}
